package query

import (
	"net/http"
	"strings"

	"github.com/glennliao/apijson-go/config"
	"github.com/glennliao/apijson-go/consts"
	"github.com/glennliao/apijson-go/model"
	"github.com/gogf/gf/v2/container/gset"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/util/gconv"
	"github.com/samber/lo"
)

// 解析查询节点请求 解析节点请求内容
func J解析查询节点请求(请求映射 model.Map, 是列表 bool) (引用映射 model.MapStrStr, 条件 model.MapStrAny, 控制映射 model.Map) {
	引用映射 = model.MapStrStr{}
	控制映射 = model.Map{}
	条件 = model.MapStrAny{}
	for 键, 值 := range 请求映射 {

		if strings.HasSuffix(键, consts.H函数键后缀) {
			continue
		}

		if strings.HasSuffix(键, consts.Y引用键后缀) { // 引用
			引用映射[键[0:len(键)-1]] = gconv.String(值)
		} else if strings.HasPrefix(键, consts.K控制键前缀) { // @column等ctrl字段
			控制映射[键] = 值
		} else {
			if 是列表 {
				switch 键 {
				case consts.Y页码, consts.S数量, consts.C查询:
					// 分页字段不传递到sqlExecutor
					continue
				}
			}

			条件[键] = 值
		}
	}
	return
}

func J检查访问权限(节点 *J节点) (有权限 bool, 条件结果 *config.T条件结果, 错误 error) {
	访问角色 := 节点.Z执行器配置.H获取访问角色列表()
	if 错误 != nil {
		return false, nil, 错误
	}

	if !lo.Contains(访问角色, 节点.J角色) {
		g.Log().Debug(节点.S上下文, 节点.J键, "role:", 节点.J角色, "accessRole", 访问角色, " -> deny")
		return false, nil, 错误
	}

	条件结果 = config.X新建条件结果()

	_请求 := model.Map{}
	for 键, 值 := range 节点.Q请求数据 {
		if !strings.HasSuffix(键, consts.H函数键后缀) && !strings.HasSuffix(键, consts.Y引用键后缀) && !strings.HasPrefix(键, consts.K控制键前缀) {
			键 = 节点.C查询上下文.S数据库字段风格(节点.S上下文, 节点.J键, 键)
		}
		_请求[键] = 值
	}

	节点.Q请求数据 = _请求
	错误 = 节点.C查询上下文.F访问条件(节点.S上下文, config.T条件请求{
		F访问名称:    节点.J键,
		B表访问角色列表: 访问角色,
		Q请求方法:    http.MethodGet,
		J节点请求数据:  节点.Q请求数据,
		J节点角色:    节点.J角色,
	}, 条件结果)

	return true, 条件结果, 错误
}

func H获取列值列表(列表 []model.Map, 列名 string) []any {
	集合 := gset.New()
	for _, 项 := range 列表 {
		集合.Add(gconv.String(项[列名]))
	}
	return 集合.Slice()
}

func S设置需要总数(节点 *J节点) {
	节点.X需要总数 = true
	if 节点.L类型 == J节点类型结构 {
		S设置需要总数(节点.Z子节点[节点.Z主表键])
	}
}

// 设置节点角色 设置节点的@role, 根据 config.DefaultRoleFunc 获取节点最终的@role
func S设置节点角色(节点 *J节点, 表名 string, 父节点角色 string) {
	角色, 存在 := 节点.Q请求数据[consts.J角色]

	if 节点.L类型 != J节点类型查询 {
		if !存在 {
			节点.J角色 = 父节点角色
		} else {
			节点.J角色 = gconv.String(角色)
		}
	} else {
		if 存在 {
			节点.J角色, _ = 节点.C查询上下文.C查询配置.H获取默认角色函数()(节点.S上下文, config.M角色请求{
				F访问名称: 表名,
				J节点角色: gconv.String(角色),
			})
		} else {
			节点.J角色, _ = 节点.C查询上下文.C查询配置.H获取默认角色函数()(节点.S上下文, config.M角色请求{
				F访问名称: 表名,
				J节点角色: 父节点角色,
			})
		}
	}
}

// 分析引用 分析依赖, 将依赖关系保存到prerequisites中
func F分析引用(父节点 *J节点, 依赖关系 *[][]string) {
	// 分析依赖关系, 让无依赖的先执行，然后在执行后续的
	for _, 节点 := range 父节点.Z子节点 {
		for _, 引用节点 := range 节点.Y引用键映射 {
			*依赖关系 = append(*依赖关系, []string{节点.L路径, 引用节点.J节点.L路径})
		}
		F分析引用(节点, 依赖关系)
	}
}
